package hydrap2p.manager;

import hydrap2p.Service;
import hydrap2p.ServiceLocation;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

public class ServiceListeners {

	private Map<ServiceLocation,ServiceListenerEvents> listeners;

	private Timer pinger;
	private PingTimer pingtimer;
	private final int pingerdelay=5000;
	
	public ServiceListeners()
	{
		listeners=new TreeMap<ServiceLocation,ServiceListenerEvents>();

		// start pinging our known listeners
		pinger=new Timer();
		pingtimer=new PingTimer();
		pinger.schedule(pingtimer, pingerdelay, pingerdelay);
	}
	
	public void shutdown()
	{
		pingtimer.shutdown();
		pinger.cancel();
	}
	
	private class PingTimer extends TimerTask {

		private TimeoutChecker timeouts;
		public PingTimer()
		{
			timeouts=new TimeoutChecker();
			timeouts.start();
		}
		
		@Override
		public void run() {
			// check the listeners
			List<ServiceLocation> mylisteners;
			synchronized(listeners)
			{
				mylisteners=new LinkedList<ServiceLocation>(listeners.keySet());
			}
			for (ServiceLocation there:mylisteners)
			{
				TSocket transport=new TSocket(there.address,there.port);
				timeouts.setCurrentConnection(transport);
				try {
					transport.open();
					Service.Client connection=new Service.Client(new TBinaryProtocol(transport));
					connection.ping();
					connection.getOutputProtocol().getTransport().close();
				} catch (TException e) {
					System.err.println("Failed to ping listener at "+there.address+":"+there.port+", removing");
					removeListener(there);
				}
				timeouts.finishedCurrentConnect();
			}

		}
		
		public void shutdown()
		{
			timeouts.shutdown();
		}
	}
	
	public boolean addListener(ServiceLocation listener, Map<String,ServiceLocation> locations)
	{
		synchronized(listeners)
		{
			if (!listeners.containsKey(listener))
			{
				try {
					ServiceListenerEvents temp=new ServiceListenerEvents(listener);
					for (Map.Entry<String, ServiceLocation> location : locations.entrySet())
						temp.newService(location.getKey(), location.getValue());
					temp.process();
					listeners.put(listener, temp);
					return true;
				} catch (TException e) {
					// Failed to add listener, oh well
					return false;
				}
			}
		}
		return false;
	}

	public void removeListener(ServiceLocation location)
	{
		synchronized(listeners)
		{
			listeners.remove(location);
		}
	}
	
	public void newService(String name, ServiceLocation location)
	{
		// notify listeners about new service
		Map<ServiceLocation,ServiceListenerEvents> mylisteners;
		synchronized(listeners)
		{
			mylisteners=new TreeMap<ServiceLocation,ServiceListenerEvents>(listeners);
		}
		for (Map.Entry<ServiceLocation,ServiceListenerEvents> listener:mylisteners.entrySet())
		{
			listener.getValue().newService(name, location);
		}
		process();
	}
	
	public void delService(String name)
	{

		// notify listeners about removed service
		Map<ServiceLocation,ServiceListenerEvents> mylisteners;
		synchronized(listeners)
		{
			mylisteners=new TreeMap<ServiceLocation,ServiceListenerEvents>(listeners);
		}
		for (Map.Entry<ServiceLocation,ServiceListenerEvents> listener:mylisteners.entrySet())
		{
			listener.getValue().delService(name);
		}
		process();
	}
	
	private void process()
	{
		Map<ServiceLocation,ServiceListenerEvents> mylisteners;
		synchronized(listeners)
		{
			mylisteners=new TreeMap<ServiceLocation,ServiceListenerEvents>(listeners);
		}
		for (Map.Entry<ServiceLocation,ServiceListenerEvents> listener:mylisteners.entrySet())
		{
			try {
				listener.getValue().process();
			} catch (TException e) {
				removeListener(listener.getKey());
			}
		}
	}
	
}
